import { ethers } from 'ethers'
import { Contract, Provider } from 'ethers-multicall'
import ERC20ABI from './abi/erc20.js'

const staking_addr = "0x84a8F8f8B874177aDA60f92f01D6c2a5EEd93e10"
const v2_addr = "0xAa2a1b249e1464419c9A6f10Ae24d5de2F874d95"
const bmaddr = "0xF19e3Df44169ff7C847ffA71f1E714B6D006B3Cb"
const pairaddr = "0x827825Af827FD85c8395332B82CB1e56FD6CfBEa"
const lockaddr = "0x83754490C27FFA2716B8CB6a6BE067fa62ad0978"
const factory_l1_addr = "0xC49A47ED862bf0E3cD88946cB6Bf491Dcb996dd9"
const ADDRESS0 = '0x0000000000000000000000000000000000000000'
const ROUTER = '0x10ED43C718714eb63d5aA57B78B54704E256024E'
const WBNB = '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c'
const USDT = '0x55d398326f99059fF775485246999027B3197955'
const WEIHGT = '0xD1797D35801e87c1B487F77330565c662Cb369E2'

const IPAIR = [
	"function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast)"
]
const IWIEIGHT = [
	"function userStakeInfo(address account) public view returns (uint256)",
	"function totalStakeAmount() public view returns (uint256)",
]

const IV2 = [
	"function stakeWithInviter(uint256 amount, uint8 slot, uint256 minAmountOut, address inviter) external",
	"function stake60(uint256 amount_usdt, uint256 amount_bm, address inviter) external",
	"function unstake(uint256 index) external",
	"function canIn() public view returns (bool)",
	"function isMember(address) public view returns (bool)",
	"function balanceOf(address) external view returns (uint256)",
	"function parentOf(address) external view returns (address)",
	"function eduOf(address) external view returns (uint256)",
	"function getFlags() public view returns (bool[7] memory)"
]

const swapABI = [
	"function getAmountsOut(uint256 amountIn, address[] calldata path) external view returns (uint256[] memory amounts)",
	"function getAmountsIn(uint256 amountOut, address[] calldata path) external view returns (uint256[] memory amounts)",
	"function swapExactTokensForETHSupportingFeeOnTransferTokens(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline ) external",
	"function swapExactTokensForTokensSupportingFeeOnTransferTokens(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline) external",
	"function addLiquidity(address tokenA, address tokenB, uint amountADesired, uint amountBDesired, uint amountAMin, uint amountBMin, address to, uint deadline ) external returns (uint amountA, uint amountB, uint liquidity)"
]

const ISK = [
	"function swapUForExactBms(uint256 amountOut, uint256 amountInMax, address inviterAddr) external",
	"function swapBNBForExactBms(uint256 amountOut, address inviterAddr) external payable",
	"function swapExactUForBms(uint256 amountIn, uint256 amountOutMin, address inviterAddr) external",
	"function swapExactBNBForBms(uint256 amountOutMin, address inviterAddr) external payable",
]

const mullcall = async () => {
	if (!window.ethereum) return { error: true, msg: 'please connect wallet' }
	const provider = new ethers.providers.Web3Provider(window.ethereum)
	const account = await connectMetamask()
	if (typeof account !== 'string') return account
	const ethcallProvider = new Provider(provider);

	await ethcallProvider.init(); // Only required when `chainId` is not provided in the `Provider` constructor

	const usdContract = new Contract(USDT, ERC20ABI);
	const bmContract = new Contract(bmaddr, ERC20ABI);
	const lockContract = new Contract(lockaddr, ERC20ABI);
	const pairCntract = new Contract(pairaddr, IPAIR);

	const bmBalanceCall = bmContract.balanceOf(account);
	const usdBalanceCall = usdContract.balanceOf(account);
	const lockedCall = lockContract.balanceOf(account);
	const usdApproveRouterCall = usdContract.allowance(account, ROUTER);
	const bmApproveRouterCall = bmContract.allowance(account, ROUTER);
	const usdApproveSkCall = usdContract.allowance(account, staking_addr);
	const bmApproveSkCall = bmContract.allowance(account, staking_addr);
	const ethBalanceCall = ethcallProvider.getEthBalance(account);
	const reservesCall = pairCntract.getReserves();

	const [bnbbal, bmBalance, usdBalance, usdapprRou, bmapprRou, usdappoeSk, bmappoeSk, reverses, lockedAmount] = await ethcallProvider.all([
		ethBalanceCall,
		bmBalanceCall, usdBalanceCall,
		usdApproveRouterCall, bmApproveRouterCall,
		usdApproveSkCall, bmApproveSkCall,
		reservesCall,
		lockedCall
	]);



	return {
		bmBalance: ethers.utils.formatEther(bmBalance),
		usdBalance: ethers.utils.formatEther(usdBalance),
		usdapprRou: ethers.utils.formatEther(usdapprRou),
		bmapprRou: ethers.utils.formatEther(bmapprRou),
		usdappoeSk: ethers.utils.formatEther(usdappoeSk),
		bmappoeSk: ethers.utils.formatEther(bmappoeSk),
		bnbbal: ethers.utils.formatEther(bnbbal),
		lockedAmount: ethers.utils.formatEther(lockedAmount),
		reverseUSDT: ethers.utils.formatEther(reverses[0]),
		reverseBM: ethers.utils.formatEther(reverses[1]),
	}
}

const getWeight = async () => {
	if (!window.ethereum) return { error: true, msg: 'please connect wallet' }
	const provider = new ethers.providers.Web3Provider(window.ethereum)
	const account = await connectMetamask()
	if (typeof account !== 'string') return account
	const ethcallProvider = new Provider(provider);

	await ethcallProvider.init(); // Only required when `chainId` is not provided in the `Provider` constructor

	const invContract = new Contract(WEIHGT, IWIEIGHT);

	const balCall = invContract.userStakeInfo(account);
	const totalCall = invContract.totalStakeAmount();
	const [bal, total] = await ethcallProvider.all([balCall, totalCall]);
	return {
		bal: ethers.utils.formatEther(bal),
		total: ethers.utils.formatEther(total),
	}
}

const getv2call = async (yaoqingdizhi) => {
	if (!window.ethereum) return { error: true, msg: 'please connect wallet' }
	const provider = new ethers.providers.Web3Provider(window.ethereum)
	const account = await connectMetamask()
	if (typeof account !== 'string') return account
	const ethcallProvider = new Provider(provider);
	await ethcallProvider.init(); // Only required when `chainId` is not provided in the `Provider` constructor

	const v2Contract = new Contract(v2_addr, IV2);
	const usdtContract = new Contract(USDT, IV2);
	const bmContract = new Contract(bmaddr, ERC20ABI);

	const balCall = v2Contract.balanceOf(account);
	const flagCall = v2Contract.getFlags();
	const eduCall = v2Contract.eduOf(account);
	const memberCall = v2Contract.isMember(yaoqingdizhi);
	const parentCall = v2Contract.parentOf(account);
	const canInCall = v2Contract.canIn();
	const usdtCall = usdtContract.balanceOf(account);
	const bmCall = bmContract.balanceOf(account);

	const [bal, usdtbal, flags, edu, parentOf, bmBal, canIn, isMember] = await ethcallProvider.all(
		[balCall, usdtCall, flagCall, eduCall, parentCall, bmCall, canInCall,memberCall]);
	return {
		bal: ethers.utils.formatEther(bal),
		usdtbal: ethers.utils.formatEther(usdtbal),
		edu: ethers.utils.formatEther(edu),
		flags,
		parentOf,
		bmBal,
		ustdAmount: usdtbal,
		canIn,
		isMember
	}
}


const connectMetamask = async () => {
	if (!window.ethereum) return { error: true, msg: "Please link to the wallet environment for use" }
	let addressArray;
	try {
		addressArray = await window.ethereum.request({
			method: "eth_requestAccounts",
		});
		if (addressArray.length !== 0) {
			return addressArray[0];
		}
	} catch (connectError) {
		return {
			error: true,
			msg: connectError,
		};
	}
};

const buyToken = async (amountIn, amountOut, jiaodian, isusd, usdappoeSk, parent) => {
	if (!window.ethereum) return { error: true, msg: 'please connect wallet' }
	const provider = new ethers.providers.Web3Provider(window.ethereum)
	const account = await connectMetamask()
	if (typeof account !== 'string') return account
	const signer = provider.getSigner()
	let amountA = ethers.utils.parseEther(amountIn)
	const amountBm = ethers.utils.parseEther(amountOut)
	const sk = new ethers.Contract(staking_addr, ISK, signer)

	if (isusd) {
		const usdt = new ethers.Contract(USDT, ERC20ABI, signer)
		const now_bal = await usdt.balanceOf(account)
		const allw = ethers.utils.parseEther(usdappoeSk)

		if (amountA.gt(allw)) {
			const tx = await usdt.approve(staking_addr, amountA.mul('2'))
			await tx.wait()
		}
		if (jiaodian == 'bm') {
			const outR = amountA.mul('103').div('100')
			if (outR.gt(now_bal)) {
				outR = now_bal
			}
			const tx = await sk.swapUForExactBms(amountBm, outR, parent)
			const r = await tx.wait()
			return r
		} else {
			if (amountA.gt(now_bal)) {
				amountA = now_bal
			}
			const tx = await sk.swapExactUForBms(amountA, amountBm.mul('80').div('100'), parent)
			const r = await tx.wait()
			return r
		}
	} else {
		if (jiaodian == 'bm') {
			const tx = await sk.swapBNBForExactBms(amountBm, parent, { value: amountA.mul('102').div('100') })
			const r = await tx.wait()
			return r
		} else {
			const tx = await sk.swapExactBNBForBms(amountBm.mul('80').div('100'), parent, { value })
			const r = await tx.wait()
			return r
		}
	}
}

const stake_usdt = async (amountIn, slot, parent) => {
	if (!window.ethereum) return { error: true, msg: 'please connect wallet' }
	const provider = new ethers.providers.Web3Provider(window.ethereum)
	const account = await connectMetamask()
	if (typeof account !== 'string') return account
	const signer = provider.getSigner()
	const amount = ethers.utils.parseEther(amountIn)

	const sk = new ethers.Contract(v2_addr, IV2, signer)
	const usdt = new ethers.Contract(USDT, ERC20ABI, signer)

	const allw = await usdt.allowance(account, v2_addr);

	if (amount.gt(allw)) {
		const tx = await usdt.approve(v2_addr, amount)
		await tx.wait()
	}
	const router = new ethers.Contract(ROUTER, swapABI, provider)
	const [, amount1] = await router.getAmountsOut(amount.div(2), [USDT, bmaddr])
	const pMin = amount1.mul(80).div(100);

	const tx = await sk.stakeWithInviter(amount, slot, pMin, parent);
	const r = await tx.wait()
	return r
}

const stake_lp = async (amountUsdt, amountBm, parent) => {
	if (!window.ethereum) return { error: true, msg: 'please connect wallet' }
	const provider = new ethers.providers.Web3Provider(window.ethereum)
	const account = await connectMetamask()
	if (typeof account !== 'string') return account
	const signer = provider.getSigner()

	const sk = new ethers.Contract(v2_addr, IV2, signer)
	const usdt = new ethers.Contract(USDT, ERC20ABI, signer)
	const bm = new ethers.Contract(bmaddr, ERC20ABI, signer)

	const allwU = await usdt.allowance(account, v2_addr);
	if (amountUsdt.gt(allwU)) {
		const tx = await usdt.approve(v2_addr, amountUsdt)
		await tx.wait()
	}
	const allwB = await bm.allowance(account, v2_addr);
	if (amountBm.gt(allwB)) {
		const tx = await bm.approve(v2_addr, amountBm)
		await tx.wait()
	}

	const tx = await sk.stake60(amountUsdt, amountBm, parent);
	const r = await tx.wait()
	return r
}

const unstake = async (_index) => {
	if (!window.ethereum) return { error: true, msg: 'please connect wallet' }
	const provider = new ethers.providers.Web3Provider(window.ethereum)
	const signer = provider.getSigner()
	const sk = new ethers.Contract(v2_addr, IV2, signer)
	const tx = await sk.unstake(_index);
	const r = await tx.wait()
	return r
}


const getBmAmountAddLp = async (amountUsdt) => {
	if (!window.ethereum) return { error: true, msg: 'please connect wallet' }
	const provider = new ethers.providers.Web3Provider(window.ethereum)

	const pair = new ethers.Contract(pairaddr, IPAIR, provider)
	const [reverseU, reverseB] = await pair.getReserves()
	return ethers.utils.parseEther(amountUsdt).mul(reverseB).div(reverseU)
}


const sellToken = async (amountbm, isusd, bmAllow) => {
	if (!window.ethereum) return { error: true, msg: 'please connect wallet' }
	const provider = new ethers.providers.Web3Provider(window.ethereum)
	const account = await connectMetamask()
	if (typeof account !== 'string') return account

	const signer = provider.getSigner()

	const bm = new ethers.Contract(bmaddr, ERC20ABI, signer)
	const lock = new ethers.Contract(lockaddr, ERC20ABI, signer)

	const allwB = ethers.utils.parseEther(bmAllow)
	let amountB = ethers.utils.parseEther(amountbm)

	const now_bal = await bm.balanceOf(account)
	const now_lock = await lock.balanceOf(account)
	let auctl = ethers.BigNumber.from('0')
	if (now_bal.gt(now_lock)) {
		auctl = now_bal.sub(now_lock)
	}

	if (amountB.gt(auctl)) {
		amountB = auctl
	}

	if (amountB.gt(allwB)) {
		const tx = await bm.approve(ROUTER, amountB)
		await tx.wait()
	}
	const router = new ethers.Contract(ROUTER, swapABI, signer)
	console.log({ amountbm })
	if (isusd) {
		const [, amount1] = await router.getAmountsOut(amountB, [bmaddr, USDT])
		const pMin = amount1.mul(55).div(100);
		const tx = await router.swapExactTokensForTokensSupportingFeeOnTransferTokens(
			amountB, pMin, [bmaddr, USDT], account, Math.floor(Date.now() / 1000) + 600
		)
		const r = await tx.wait()
		return r
	} else {
		const [, amount1] = await router.getAmountsOut(amountB, [bmaddr, USDT, WBNB])
		const pMin = amount1.mul(55).div(100);
		const tx = await router.swapExactTokensForETHSupportingFeeOnTransferTokens(
			amountB, pMin, [bmaddr, USDT, WBNB], account, Math.floor(Date.now() / 1000) + 600
		)
		const r = await tx.wait()
		return r
	}
}

const addPiarLiq = async (amountusdt, amountbm, usdAllow, bmAllow) => {
	if (!window.ethereum) return { error: true, msg: 'please connect wallet' }
	const provider = new ethers.providers.Web3Provider(window.ethereum)
	const account = await connectMetamask()
	if (typeof account !== 'string') return account

	const signer = provider.getSigner()

	const usdt = new ethers.Contract(USDT, ERC20ABI, signer)
	const bm = new ethers.Contract(bmaddr, ERC20ABI, signer)

	const allwA = ethers.utils.parseEther(usdAllow)
	const allwB = ethers.utils.parseEther(bmAllow)

	const amountA = ethers.utils.parseEther(amountusdt)
	const amountB = ethers.utils.parseEther(amountbm)

	if (amountA.gt(allwA)) {
		const tx = await usdt.approve(ROUTER, amountA)
		await tx.wait()
	}

	if (amountB.gt(allwB)) {
		const tx = await bm.approve(ROUTER, amountB)
		await tx.wait()
	}

	console.log({ amountusdt, amountbm })

	const router = new ethers.Contract(ROUTER, swapABI, signer)
	const tx = await router.addLiquidity(
		USDT, bmaddr,
		amountA, amountB,
		amountA.mul('97').div('100'), amountB.mul('97').div('100'),
		account,
		Math.floor(Date.now() / 1000) + 600
	)

	const r = await tx.wait()
	return r
}

const getAmountsOut = async (amountin, addresses) => {
	if (!window.ethereum) return { error: true, msg: 'please connect wallet' }
	const provider = new ethers.providers.Web3Provider(window.ethereum)
	const account = await connectMetamask()
	if (typeof account !== 'string') return account

	const router = new ethers.Contract(ROUTER, swapABI, provider)
	const amountOuts = await router.getAmountsOut(amountin, addresses)
	return amountOuts[amountOuts.length - 1]
}

const getReserve = async () => {
	if (!window.ethereum) return { error: true, msg: 'please connect wallet' }
	const provider = new ethers.providers.Web3Provider(window.ethereum)
	const account = await connectMetamask()
	if (typeof account !== 'string') return account

	const pairContract = new ethers.Contract(pairaddr, IPAIR, provider)
	const res = await pairContract.getReserves()
	return {
		reserveusdt: ethers.utils.formatEther(res[0]),
		reservebm: ethers.utils.formatEther(res[1]),
	}
}

function isNumeric(str) {
	if (typeof str !== "string") return false; // 必须是字符串
	return str.trim() !== "" && !isNaN(str);
}


export {
	connectMetamask,
	getAmountsOut,
	ADDRESS0,
	mullcall,
	bmaddr,
	USDT,
	WBNB,
	addPiarLiq,
	getReserve,
	sellToken,
	getWeight,
	buyToken,
	isNumeric,
	getv2call,
	stake_usdt,
	stake_lp,
	getBmAmountAddLp,
	unstake
}